# QueryStatement.java

    package org.codefilarete.stalactite.query.model;

    import javax.annotation.Nullable;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;

    import org.codefilarete.tool.bean.Objects;
    import org.codefilarete.tool.collection.Iterables;
    import org.codefilarete.tool.collection.KeepOrderSet;

    /**
     * @author Guillaume Mary
     */
    public interface QueryStatement extends SelectablesPod {

    	/**
    	 * Wraps current instance to make it appendable into a From clause
    	 * @return an instance containing current instance and appendable into a From clause
    	 */
    	default PseudoTable asPseudoTable() {
    		return asPseudoTable(null);
    	}

    	/**
    	 * Wraps current instance to make it appendable into a From clause
    	 *
    	 * @param name an optional name that serves as an alias of this instance into the From clause
    	 * @return an instance containing current instance and appendable into a From clause
    	 */
    	default PseudoTable asPseudoTable(@Nullable String name) {
    		return new PseudoTable(this, name);
    	}

    	/**
    	 * Wrapper of {@link Query} and {@link Union} (as {@link QueryStatement}) to make them capable of being added to a {@link From} clause.
    	 *
    	 * @author Guillaume Mary
    	 */
    	class PseudoTable implements Fromable {

    		@Nullable
    		private final String name;

    		private final QueryStatement queryStatement;

    		private final KeepOrderSet<PseudoColumn<Object>> columns = new KeepOrderSet<>();

    		private final Map<Selectable<?>, String> aliases = new HashMap<>();

    		public PseudoTable(QueryStatement queryStatement, @Nullable String name) {
    			this.name = name;
    			this.queryStatement = queryStatement;
    			Map<Selectable<?>, String> unionAliases = queryStatement.getAliases();
    			for (Selectable<?> column : queryStatement.getColumns()) {
    				PseudoColumn<?> newPseudoColumn = new PseudoColumn<>(this, column.getExpression(), column.getJavaType());
    				columns.add((PseudoColumn<Object>) newPseudoColumn);
    				String alias = unionAliases.get(column);
    				if (alias != null) {
    					this.aliases.put(newPseudoColumn, alias);
    				}
    			}
    		}

    		public QueryStatement getQueryStatement() {
    			return queryStatement;
    		}

    		@Nullable
    		@Override
    		public String getName() {
    			return name;
    		}

    		@Override
    		public String getAbsoluteName() {
    			return getName();
    		}

    		@Override
    		public Set<PseudoColumn<?>> getColumns() {
    			return (Set) this.columns;
    		}

    		@Override
    		public Map<Selectable<?>, String> getAliases() {
    			return this.aliases;
    		}

    		/**
    		 * @return union columns per name
    		 */
    		@Override
    		public Map<String, PseudoColumn<?>> mapColumnsOnName() {
    			return Iterables.map(getColumns(), objectPseudoColumn -> Objects.preventNull(aliases.get(objectPseudoColumn), objectPseudoColumn.getExpression()));
    		}
    	}

    	class PseudoColumn<O> implements JoinLink<Fromable, O> {

    		private final SelectablesPod owner;	// Union or Query

    		private final String name;

    		private final Class<O> javaType;

    		public PseudoColumn(SelectablesPod owner, String name, Class<O> javaType) {
    			this.owner = owner;
    			this.name = name;
    			this.javaType = javaType;
    		}

    		/**
    		 * To be called only for pseudo-column in a {@link PseudoTable}, else would throw a {@link ClassCastException}
    		 * @return {@link PseudoTable} owning this column
    		 */
    		@Override
    		public Fromable getOwner() {
    			return (Fromable) owner;
    		}

    		@Override
    		public String getExpression() {
    			return name;
    		}

    		@Override
    		public Class<O> getJavaType() {
    			return javaType;
    		}
    	}
    }